import { BlockPoint, NullableBlockPoint } from "../model/BlockPoint.js";
import { Point } from "../model/Point.js";

export type MatrixData = NullableBlockPoint[][];

export class Matrix {
    static createRow(width: number): NullableBlockPoint[] {
        return Array.from(Array(width));
    }

    static createMatrix(width: number, height: number): NullableBlockPoint[][] {
        return Array.from(
            Array(height),
            () => this.createRow(width)
        );
    }

    #width: number;
    #height: number;
    #data!: MatrixData;

    constructor(width: number, height: number) {
        this.#width = width;
        this.#height = height;
        this.reset();
    }

    get width(): number {
        return this.#width;
    }

    get height(): number {
        return this.#height;
    }

    addBlockPoints(points: BlockPoint[]): void {
        points.forEach(point => this.set(point));
    }

    set(point: BlockPoint): void;
    set(x: number, y: number, point?: BlockPoint): void;
    set(first: number | BlockPoint, y?: number, point: NullableBlockPoint = null): void {
        if (typeof first === "number") {
            this.#data[y!][first] = point;
        } else {
            this.#data[first.y][first.x] = first;
        }
    }

    getFullRows(): number[] {
        const fulls = [] as number[];
        this.#data.forEach((row, i) => {
            if (row.length && row.every(t => !!t)) {
                fulls.push(i);
            }
        });
        return fulls;
    }

    removeRows(rows: number[]): void {
        if (!rows.length) {
            return;
        }

        const newData = Matrix.createMatrix(this.width, rows.length);
        let next = 0;
        this.#data.forEach((row, i) => {
            if (i === rows[next]) {
                next++;
                if (next >= rows.length) {
                    next = -1;
                }
            } else {
                row.filter(t => t).forEach(t => t?.set(undefined, newData.length));
                newData.push(row);
            }
        });

        this.#data = newData;
    }

    reset(): void {
        this.#data = Matrix.createMatrix(this.width, this.height);
    }

    fasten(): BlockPoint[] {
        return this.#data.flatMap(row => row.filter(t => t) as BlockPoint[]);
    }

    isPutable(points: Point[]): boolean {
        const { width, height } = this;

        return points.every(t => {
            if (t.x < 0 || t.x >= width || t.y < 0 || t.y >= height) {
                return false;
            }

            return !this.#data[t.y][t.x];
        });
    }
}
